Справочник по языку Rust
Назначение
Справочник-шпаргалка по Rust: типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс: [раздел](/encyclopedia/5-languages/5.13. Rust/intro).
Краткое пояснение
Любая программа на Rust состоит из одного или нескольких модулей, каждый из которых может содержать: - объявления элементов (use, mod, extern crate); - определения типов (struct, enum, union); - функции (fn); - константы (const, static); - реализации (impl); - макросы (macro_rules!, #[macro_export]).
Быстрый старт
| Задача | Команда |
|---|---|
| Новый проект | cargo new app |
| Сборка / запуск | cargo build / cargo run |
| Тесты | cargo test |
| Релиз | cargo build --release |
Справочные таблицы
Содержание справочника
- 2. Комментарии
- 3. Идентификаторы и ключевые слова
- 4. Литералы
- 5. Переменные и привязки
- 6. Типы данных
- 7. Функции
- 8. Управляющие конструкции
- 9. Владение и заимствование (основы)
- 10. Структуры (
struct) - 11. Перечисления (
enum) - 12. Трейты (
trait) - 13. Обобщения (
generics) - 14. Жизненные циклы (
lifetimes) - 15. Смарт-поинтеры
- 16. Обработка ошибок
- 17. Макросы
- 18. Модули и организация кода
- 19. Cargo — система сборки и управления зависимостями
- 20. Тестирование
- 21. Атрибуты
- 22. FFI — взаимодействие с C
- 23. Асинхронное программирование
- 24. Многопоточность
- 25. Небезопасный код (
unsafe) - 26. Расширенные паттерны сопоставления
- 27. Стандартная библиотека — обзор ключевых модулей
- 28. Производительность и профилирование
- 29. Обработка ошибок на продакшене
- 30. Лучшие практики и идиомы Rust
- 31. Инструменты экосистемы
- 32. Отладка и диагностика
Любая программа на Rust состоит из одного или нескольких модулей, каждый из которых может содержать:
- объявления элементов (
use,mod,extern crate); - определения типов (
struct,enum,union); - функции (
fn); - константы (
const,static); - реализации (
impl); - макросы (
macro_rules!,#[macro_export]).
Точка входа в исполняемую программу — функция main:
fn main() {
println!("Hello, world!");
}
Для библиотек точка входа не обязательна.
2. Комментарии
Rust поддерживает три вида комментариев:
- Однострочные:
// это комментарий - Многострочные:
/* это многострочный комментарий */ - Документационные:
/// это документация для следующего элемента
Документационные комментарии обрабатываются rustdoc и могут содержать Markdown.
3. Идентификаторы и ключевые слова
Идентификаторы
Идентификаторы в Rust:
- начинаются с буквы или символа подчеркивания
_; - могут содержать буквы, цифры, подчеркивания;
- чувствительны к регистру;
- не могут совпадать с зарезервированными ключевыми словами.
Примеры корректных идентификаторов:
counter_tempmy_var_123π(Unicode разрешён)
Зарезервированные ключевые слова
Всегда зарезервированные:
as, break, const, continue, crate, else, enum, extern, false, fn, for, if, impl, in, let, loop, match, mod, move, mut, pub, ref, return, self, Self, static, struct, super, trait, true, type, unsafe, use, where, while
Зарезервированные для будущего использования:
abstract, become, box, do, final, macro, override, priv, typeof, unsized, virtual, yield
Эти слова нельзя использовать как идентификаторы без экранирования через обратные кавычки (raw identifiers): r#do.
4. Литералы
Целочисленные литералы
- Десятичные:
42 - Шестнадцатеричные:
0xFF - Восьмеричные:
0o77 - Двоичные:
0b101010 - Подчёркивания для читаемости:
1_000_000
Можно указывать суффиксы типа:
i8,i16,i32,i64,i128,isizeu8,u16,u32,u64,u128,usize
Пример: 42u32, 0xFF_i8
Числа с плавающей точкой
- Десятичные:
3.14,2.0 - Экспоненциальная форма:
1e10,2.5E-3 - Суффиксы:
f32,f64
Пример: 3.1415_f64
Булевы литералы
truefalse
Символы
- Одинарные кавычки:
'a','α','\n','\u{1F600}' - Размер: всегда 4 байта (UTF-32)
Строковые литералы
- Двойные кавычки:
"hello" - Экранирование:
\n,\t,\",\\ - Raw-строки:
r#"..."#,r##"..."##(количество#должно совпадать) - Многострочные строки поддерживаются напрямую
Пример:
let s = "Line 1\nLine 2";
let raw = r#"C:\Users\Name"#;
Byte-литералы
- Байтовые строки:
b"hello"→&[u8; 5] - Байтовые символы:
b'A'→u8
Массивы и кортежи
- Массив:
[1, 2, 3],[0; 10](массив из 10 нулей) - Кортеж:
(1, "hello", true)
5. Переменные и привязки
Объявление переменной происходит через ключевое слово let:
let x = 5;
let mut y = 10;
y = 15; // разрешено только для mut
Особенности:
- Переменные неизменяемы по умолчанию.
- Изменяемость указывается явно через
mut. - Привязки могут быть переопределены (shadowing):
let x = 5;
let x = x + 1; // новая привязка
let x = "hello"; // тип изменился — допустимо
Shadowing не требует mut и создаёт новую переменную с тем же именем.
6. Типы данных
Скалярные типы
Целые числа
- Со знаком:
i8,i16,i32,i64,i128,isize - Без знака:
u8,u16,u32,u64,u128,usize
isize и usize зависят от архитектуры (32 или 64 бита).
Числа с плавающей точкой
f32,f64(по умолчаниюf64)
Булевый тип
bool: значенияtrue,false
Символы
char: Unicode scalar value (например,'A','🦀','\u{1F4A9}')
Составные типы
Кортежи (tuples)
Фиксированная длина, элементы разных типов:
let tup: (i32, f64, char) = (500, 6.4, 'x');
let (x, y, z) = tup; // деструктуризация
let second = tup.1; // доступ по индексу
Массивы
Фиксированная длина, все элементы одного типа:
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10]; // массив из 10 нулей
Доступ по индексу: arr[0]. Выход за границы вызывает панику в режиме отладки.
7. Функции
Объявление функции:
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
Особенности:
- Аргументы указываются с типами.
- Возвращаемый тип указывается после стрелки
->. - Последнее выражение в блоке (без точки с запятой) является возвращаемым значением.
- Явный
returnиспользуется редко.
Функции могут быть:
const fn— вычисляемые во время компиляции;unsafe fn— содержащие небезопасный код;extern "C" fn— для FFI.
Пример const fn:
const fn square(x: i32) -> i32 {
x * x
}
8. Управляющие конструкции
Условные выражения
if
if condition {
// ...
} else if other_condition {
// ...
} else {
// ...
}
if — выражение, возвращает значение:
let number = if condition { 5 } else { 6 };
Все ветви должны возвращать один и тот же тип.
match
Полное выражение сопоставления с образцом:
match value {
1 => println!("one"),
2 | 3 => println!("two or three"),
4..=10 => println!("four to ten"),
_ => println!("something else"),
}
Особенности:
- Обязательно покрытие всех вариантов (exhaustiveness).
_— wildcard-образец.- Можно использовать диапазоны (
..=,..), охранники (if), деструктуризацию.
Циклы
loop
Бесконечный цикл:
let mut counter = 0;
loop {
counter += 1;
if counter == 10 {
break;
}
}
Может возвращать значение:
let result = loop {
break 42;
};
while
while condition {
// ...
}
for
Итерация по итератору:
for i in 0..5 {
println!("{}", i);
}
for item in vec.iter() {
println!("{}", item);
}
for автоматически берёт владение, заимствование или ссылку в зависимости от контекста.
9. Владение и заимствование (основы)
Rust управляет памятью через систему владения:
- Каждое значение имеет одного владельца.
- Когда владелец выходит из области видимости, значение уничтожается.
- Значение можно переместить (
move) — передача владения. - Можно создать ссылку (
&T) — заимствование. - Изменяемые ссылки (
&mut T) позволяют изменять данные. - В любой момент времени допускается либо одна изменяемая ссылка, либо любое количество неизменяемых.
Пример:
let s1 = String::from("hello");
let s2 = s1; // s1 больше не действителен
// println!("{}", s1); // ошибка!
let s = String::from("world");
let r1 = &s;
let r2 = &s;
// let r3 = &mut s; // ошибка: нельзя одновременно иметь & и &mut
10. Структуры (struct)
Структуры — это пользовательские составные типы данных. Rust поддерживает три вида структур.
10.1. Классические структуры с именованными полями
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
Создание экземпляра:
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
Обновление через синтаксис обновления структуры:
let user2 = User {
email: String::from("another@example.com"),
..user1 // остальные поля скопированы из user1
};
После такого обновления
user1.usernameстановится недоступным, еслиStringне реализуетCopy.
10.2. Кортежные структуры (tuple structs)
Поля не имеют имён, только позиции:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
Типы Color и Point считаются разными, даже если содержат одинаковые данные.
10.3. Единичные структуры (unit-like structs)
Структуры без полей:
struct AlwaysEqual;
let subject = AlwaysEqual;
Используются, когда нужен тип без данных, но с реализацией трейтов.
10.4. Методы для структур
Методы определяются в блоке impl:
impl User {
fn new(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
fn greet(&self) -> String {
format!("Hello, {}!", self.username)
}
fn activate(&mut self) {
self.active = true;
}
}
&self— заимствование экземпляра (неизменяемое).&mut self— изменяемое заимствование.self— передача владения (редко используется).
Вызов:
let mut user = User::new("a@b.com".to_string(), "alice".to_string());
user.activate();
println!("{}", user.greet());
11. Перечисления (enum)
Перечисления определяют тип, который может быть одним из нескольких вариантов.
11.1. Базовое объявление
enum IpAddrKind {
V4,
V6,
}
Использование:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
11.2. Перечисления с данными
Варианты могут содержать данные:
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
Можно использовать разные типы в разных вариантах:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
11.3. Методы для перечислений
Перечисления также могут иметь impl-блоки:
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quitting..."),
_ => println!("Another message"),
}
}
}
11.4. Option<T> — стандартное перечисление
enum Option<T> {
Some(T),
None,
}
Используется вместо null. Обязательная обработка обоих случаев.
Пример:
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
11.5. Result<T, E> — обработка ошибок
enum Result<T, E> {
Ok(T),
Err(E),
}
Используется для возврата успешного результата или ошибки.
12. Трейты (trait)
Трейты определяют поведение, которое типы могут реализовать.
12.1. Определение трейта
trait Summary {
fn summarize(&self) -> String;
}
12.2. Реализация трейта
struct NewsArticle {
headline: String,
location: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, ({})", self.headline, self.location)
}
}
12.3. Требования по реализации
- Трейт и тип должны быть определены в текущем крейте (правило орфана), либо:
- Трейт определён локально, и тип внешний → можно реализовать.
- Тип определён локально, и трейт внешний → можно реализовать.
- Оба внешние → нельзя реализовать.
12.4. Трейты с поведением по умолчанию
trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
Типы могут переопределять метод или использовать реализацию по умолчанию.
12.5. Расширение трейтов
Трейт может требовать реализации другого трейта:
trait Displayable: Summary {
fn display(&self);
}
Любой тип, реализующий Displayable, обязан реализовать и Summary.
12.6. Супер-трейты
Аналогично: trait A: B + C означает, что A требует B и C.
12.7. Ассоциированные типы
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Позволяют определять типы внутри трейта.
12.8. Обобщённые трейты и ограничения
Функции могут принимать параметры с ограничениями по трейтам:
fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
Или через where:
fn some_function<T, U>(t: T, u: U) -> i32
where
T: Summary + Clone,
U: Display,
{
// ...
}
13. Обобщения (generics)
13.1. Обобщённые функции
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
13.2. Обобщённые структуры
struct Point<T> {
x: T,
y: T,
}
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
Можно использовать несколько параметров:
struct Point<T, U> {
x: T,
y: U,
}
13.3. Обобщённые перечисления
enum Result<T, E> {
Ok(T),
Err(E),
}
13.4. Обобщённые методы
Методы могут иметь собственные параметры обобщения, независимо от структуры:
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
14. Жизненные циклы (lifetimes)
Жизненные циклы — часть системы заимствования, гарантирующая, что ссылки всегда действительны.
14.1. Аннотации жизненных циклов
Синтаксис: 'a, 'b, 'static
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Эта функция возвращает ссылку, которая живёт не дольше, чем обе входные ссылки.
14.2. Правила элиминации жизненных циклов
Компилятор применяет три правила для автоматического вывода:
- Каждый параметр-ссылка получает собственный уникальный жизненный цикл.
- Если есть один входной параметр-ссылка, его жизненный цикл присваивается всем выходным ссылкам.
- Если среди параметров есть
&selfили&mut self, их жизненный цикл присваивается всем выходным ссылкам.
Если правила не позволяют вывести — требуется явная аннотация.
14.3. Статический жизненный цикл
'static — самый длинный возможный жизненный цикл. Все строковые литералы имеют 'static:
let s: &'static str = "I have a static lifetime.";
14.4. Жизненные циклы в структурах
Структуры, содержащие ссылки, должны указывать жизненные циклы:
struct ImportantExcerpt<'a> {
part: &'a str,
}
Реализация методов:
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
15. Смарт-поинтеры
Смарт-поинтеры — это структуры данных, которые ведут себя как указатели, но предоставляют дополнительную функциональность (например, автоматическое управление памятью). В Rust смарт-поинтеры реализуют трейты Deref и Drop.
15.1. Box<T> — выделение на куче
Используется для:
- Хранения данных в куче.
- Рекурсивных типов (например, деревьев).
- Трейт-объектов (
Box<dyn Trait>).
Пример:
let b = Box::new(5);
println!("b = {}", b); // 5
Рекурсивная структура:
enum List {
Cons(i32, Box<List>),
Nil,
}
15.2. Rc<T> — подсчёт ссылок (reference counting)
Позволяет нескольким владельцам совместно использовать данные. Только для однопоточного использования.
use std::rc::Rc;
let a = Rc::new(String::from("hello"));
let b = Rc::clone(&a);
let c = Rc::clone(&a);
println!("count after creating c: {}", Rc::strong_count(&a)); // 3
Rc<T> не реализует Send или Sync — нельзя передавать между потоками.
15.3. Arc<T> — атомарный подсчёт ссылок
Аналог Rc<T>, но потокобезопасный (Arc = Atomically Reference Counted).
use std::sync::Arc;
use std::thread;
let Данные = Arc::new(vec![1, 2, 3]);
let data1 = Arc::clone(&Данные);
let handle = thread::spawn(move || {
println!("{:?}", data1);
});
handle.join().unwrap();
15.4. RefCell<T> — заимствование во время выполнения
Обходит правила заимствования на этапе компиляции, проверяя их во время выполнения.
- Позволяет иметь изменяемое заимствование даже при неизменяемой ссылке.
- Паникует при нарушении правил (например, одновременное изменяемое и неизменяемое заимствование).
use std::cell::RefCell;
let x = RefCell::new(5);
{
let mut m = x.borrow_mut();
*m += 1;
}
println!("{}", x.borrow()); // 6
15.5. Cell<T> — внутренняя изменяемость без заимствования
Поддерживает только типы, реализующие Copy. Не возвращает ссылки — работает через get/set.
use std::cell::Cell;
let c = Cell::new(10);
c.set(20);
println!("{}", c.get()); // 20
15.6. Комбинирование смарт-поинтеров
Частые комбинации:
Rc<RefCell<T>>— разделяемая изменяемость в одном потоке.Arc<Mutex<T>>— разделяемая изменяемость между потоками.Box<dyn Trait>— динамическая диспетчеризация.
16. Обработка ошибок
Rust не использует исключения. Ошибки обрабатываются через типы.
16.1. panic!
Вызывает немедленную остановку программы (в режиме отладки — с трассировкой стека).
panic!("Something went wrong!");
Используется при невозможности восстановления (например, логические ошибки).
16.2. Result<T, E>
Основной способ обработки восстанавливаемых ошибок.
fn read_file(path: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(path)
}
16.3. Оператор ?
Сокращает обработку Result:
fn main() -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string("file.txt")?;
println!("{}", content);
Ok(())
}
Оператор ?:
- Возвращает значение, если
Ok. - Возвращает
Errиз функции, еслиErr.
Можно использовать только в функциях, возвращающих Result или Option.
16.4. Преобразование ошибок
Используйте map_err, and_then, or_else:
let result = read_file("Данные.txt")
.map_err(|e| format!("Failed to read file: {}", e))?;
Для сложных случаев — создание собственного типа ошибки или использование библиотек (thiserror, anyhow).
16.5. Option<T> и безопасность
Вместо null — Option::Some(value) или Option::None.
Безопасные методы:
unwrap_or(default)unwrap_or_else(|| compute())map,and_then,filter,ok_or
17. Макросы
Макросы — это метапрограммирование на этапе компиляции.
17.1. Декларативные макросы (macro_rules!)
Похожи на шаблоны сопоставления.
macro_rules! say_hello {
() => {
println!("Hello!");
};
($name:expr) => {
println!("Hello, {}!", $name);
};
}
say_hello!(); // Hello!
say_hello!("Alice"); // Hello, Alice!
Шаблоны могут содержать повторения:
macro_rules! vec_sum {
($($x:expr),*) => {
0 $(+ $x)*
};
}
let s = vec_sum!(1, 2, 3); // 6
17.2. Процедурные макросы
Работают как функции, принимающие токены и возвращающие токены. Три типа:
17.2.1. Макросы-функции (#[proc_macro])
Используются как функции:
use proc_macro::TokenStream;
#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
"fn answer() -> u32 { 42 }".parse().unwrap()
}
Вызов:
make_answer!();
17.2.2. Макросы-атрибуты (#[proc_macro_attribute])
Применяются к элементам:
#[my_macro]
fn my_function() { }
17.2.3. Макросы-производные (#[proc_macro_derive])
Автоматически генерируют код для #[derive(...)]:
#[derive(MyTrait)]
struct MyStruct;
17.3. Встроенные макросы
println!,eprintln!,format!— форматированный вывод.vec!— создание вектора:vec![1, 2, 3],vec![0; 10].assert!,assert_eq!,debug_assert!— проверки.todo!,unimplemented!— заглушки.include_str!,include_bytes!— встраивание файлов во время компиляции.
18. Модули и организация кода
18.1. Ключевые слова
mod— объявление модуля.use— импорт путей.pub— экспорт элементов.crate— корень текущего крейта.super— родительский модуль.
18.2. Иерархия модулей
Файл main.rs:
mod Сеть {
pub mod server {
pub fn connect() {}
}
}
Или через отдельные файлы:
src/
├── main.rs
└── Сеть/
├── mod.rs
└── server.rs
mod.rs определяет содержимое модуля Сеть.
18.3. Пути
- Абсолютные:
crate::Сеть::server::connect - Относительные:
super::utils::helper
18.4. Экспорт
По умолчанию всё приватно. Для экспорта:
pub mod server;
pub fn connect() {}
pub struct Config;
19. Cargo — система сборки и управления зависимостями
19.1. Файл Cargo.toml
Основной манифест проекта. Минимальная структура:
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = "1.0"
tokio = { version = "1.0", features = ["full"] }
19.2. Секции манифеста
[package]
Обязательные поля:
name— имя крейта (должно быть валидным идентификатором Rust)version— семантическое версионирование (MAJOR.MINOR.PATCH)edition— версия языка (2015,2018,2021)
Опциональные:
authors,description,license,repository,readme,keywords,categories
[dependencies]
Зависимости времени выполнения:
[dependencies]
rand = "0.8" # любая совместимая версия
regex = "~0.3.5" # патч-версии (~0.3.x)
lazy_static = "=1.4.0" # строго эта версия
[dev-dependencies]
Зависимости только для тестов и разработки:
[dev-dependencies]
criterion = "0.5"
[build-dependencies]
Зависимости для скриптов сборки (build.rs).
[features]
Условная компиляция:
[features]
default = ["native-tls"]
native-tls = ["tokio/native-tls"]
rustls = ["tokio/rustls"]
Включение фичи:
cargo build --features "rustls"
[workspace]
Объединяет несколько крейтов:
[workspace]
members = ["crates/*", "Примеры/my_tool"]
Каждый член workspace должен иметь свой Cargo.toml.
19.3. Профили сборки
Определяют параметры компиляции:
[profile.dev]
opt-level = 0 # отладка
debug = true
overflow-checks = true
[profile.release]
opt-level = 3 # максимальная оптимизация
debug = false
lto = true # link-time optimization
codegen-units = 1 # медленнее, но лучше оптимизация
strip = true # удалять символы отладки
Доступные профили: dev, release, test, bench, doc.
19.4. Команды Cargo
| Команда | Назначение |
|---|---|
cargo build | Сборка в режиме разработки |
cargo build --release | Сборка с оптимизациями |
cargo run | Сборка и запуск |
cargo test | Запуск тестов |
cargo check | Быстрая проверка без генерации кода |
cargo doc | Генерация документации |
cargo publish | Публикация на crates.io |
cargo install | Установка бинарного крейта |
cargo update | Обновление зависимостей |
19.5. Скрипты сборки (build.rs)
Выполняются перед компиляцией. Используются для:
- Генерации кода
- Проверки системных зависимостей
- Настройки окружения
Пример:
// build.rs
use std::env;
use std::fs::File;
use std::io::Write;
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = std::path::Path::new(&out_dir).join("version.rs");
let mut f = File::create(&dest_path).unwrap();
f.write_all(b"pub const VERSION: &str = \"1.0.0\";").unwrap();
}
20. Тестирование
20.1. Модульные тесты (unit tests)
Размещаются внутри файла с тестируемым кодом:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
#[should_panic]
fn this_panics() {
panic!("Oops!");
}
#[test]
#[ignore]
fn expensive_test() {
// пропускается при обычном `cargo test`
}
}
Запуск:
cargo test
cargo test it_works
cargo test -- --ignored
20.2. Интеграционные тесты
Размещаются в директории tests/:
src/
tests/
├── integration_test.rs
└── db/
└── mod.rs
Каждый файл — отдельный крейт. Доступ к публичному API основного крейта.
// tests/integration_test.rs
use my_project::add;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
20.3. Документационные тесты
Примеры в /// комментариях автоматически тестируются:
/// Adds two numbers.
///
/// # Примеры
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
Запуск: cargo test --doc
20.4. Бенчмарки
Требуют nightly или criterion:
#[cfg(test)]
mod benches {
use super::*;
use test::Bencher;
#[bench]
fn bench_add(b: &mut Bencher) {
b.iter(|| add(2, 3));
}
}
Или через criterion (стабильный):
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "my_benchmark"
harness = false
21. Атрибуты
Атрибуты — метаданные, влияющие на компиляцию.
21.1. Встроенные атрибуты
Условная компиляция
#[cfg(target_os = "linux")]
fn platform_specific() {}
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
Оптимизация
#[inline] // рекомендовать инлайнинг
#[inline(always)] // принудительный инлайнинг
#[inline(never)] // запрет инлайнинга
Представление типов
#[repr(C)] // совместимость с C
#[repr(u8)] // для enum — явный дискриминант
#[repr(packed)] // плотная упаковка (осторожно!)
Документация
#[doc(hidden)] // скрыть из документации
#[doc(alias = "old_name")] // альтернативное имя поиска
Безопасность
#[unsafe(no_sanitize = "address")] // отключить ASan
21.2. Производные атрибуты
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point {
x: i32,
y: i32,
}
Стандартные трейты для derive:
Clone,CopyDebug,DefaultPartialEq,EqPartialOrd,OrdHashSerialize,Deserialize(изserde)
22. FFI — взаимодействие с C
22.1. Вызов Rust из C
Экспорт функции:
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
Флаги в Cargo.toml:
[lib]
crate-type = ["cdylib"] # для .so/.dll/.dylib
22.2. Вызов C из Rust
Объявление внешней функции:
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("abs(-5) = {}", abs(-5));
}
}
Использование bindgen для генерации Rust-обвязок из заголовков C.
22.3. Типы данных в FFI
Безопасные типы:
i8,u32,f64→int8_t,uint32_t,double*const T,*mut T→ указателиc_char,c_intизstd::os::raw
Строки:
- Передача в C:
CString::new("hello").unwrap().as_ptr() - Приём из C:
CStr::from_ptr(ptr).to_str().unwrap()
23. Асинхронное программирование
23.1. Основы async/await
Асинхронные функции возвращают Future:
async fn fetch_data() -> String {
"Данные".to_string()
}
Вызов:
#[tokio::main]
async fn main() {
let Данные = fetch_data().await;
println!("{}", Данные);
}
Ключевые моменты:
async fnне блокирует поток..awaitприостанавливает выполнение до завершенияFuture.- Код внутри
async fnможет быть прерван в точках.await.
23.2. Тип Future
Трейт Future определяет асинхронную операцию:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct MyFuture;
impl Future for MyFuture {
type Output = i32;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(42)
}
}
В реальном коде редко реализуется вручную — генерируется компилятором для async fn.
23.3. Рантаймы
Rust не предоставляет встроенный асинхронный рантайм. Популярные варианты:
- Tokio — промышленный, поддерживает I/O, таймеры, задачи.
- async-std — API, похожий на
std, но асинхронный. - smol — минималистичный рантайм.
Пример с Tokio (Cargo.toml):
[dependencies]
tokio = { version = "1.0", features = ["full"] }
23.4. Асинхронные задачи
Запуск задач в фоне:
tokio::spawn(async {
println!("Background task");
});
Ожидание результата:
let handle = tokio::spawn(async { 42 });
let result = handle.await.unwrap(); // 42
23.5. Асинхронные каналы
Из tokio::sync::mpsc:
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(32);
tokio::spawn(async move {
tx.send("hello").await.unwrap();
});
let msg = rx.recv().await.unwrap();
println!("{}", msg);
}
23.6. Асинхронные блокировки
tokio::sync::Mutex— асинхронная мьютекс-блокировка.- Не блокирует поток, а уступает управление.
use tokio::sync::Mutex;
let Данные = Arc::new(Mutex::new(0));
24. Многопоточность
24.1. Создание потоков
use std::thread;
let handle = thread::spawn(|| {
"Hello from thread"
});
let result = handle.join().unwrap();
println!("{}", result);
24.2. Совместный доступ к данным
Arc<T> — атомарный подсчёт ссылок
Позволяет делить владение между потоками.
Mutex<T> — взаимоисключающая блокировка
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
24.3. Каналы (std::sync::mpsc)
Межпоточная передача сообщений:
use std::sync::mpsc;
use std::thread;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("message").unwrap();
});
let received = rx.recv().unwrap();
println!("{}", received);
Типы каналов:
mpsc::channel()— один отправитель, один получатель.mpsc::Sender::clone()— несколько отправителей.
24.4. Безопасность между потоками
Трейты:
Send— тип можно передавать между потоками.Sync— ссылку на тип можно использовать из нескольких потоков.
Большинство стандартных типов реализуют Send и Sync. Исключения:
Rc<T>— неSend, неSyncRefCell<T>— неSend, неSyncMutex<T>,Arc<T>—SendиSync(если содержимое тоже)
25. Небезопасный код (unsafe)
25.1. Когда разрешён unsafe
Пять операций, требующих unsafe:
- Разыменование сырого указателя.
- Вызов небезопасной функции или метода.
- Реализация небезопасного трейта.
- Чтение или запись в статическую переменную
mut. - Доступ к полям
union.
25.2. Сырые указатели
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1: {}", *r1);
*r2 = 10;
}
Сырые указатели не проверяются на валидность.
25.3. Небезопасные функции
unsafe fn dangerous_function() {
// ...
}
fn main() {
unsafe {
dangerous_function();
}
}
Автор обязан гарантировать соблюдение инвариантов.
25.4. Небезопасные трейты
unsafe trait DangerousTrait { }
unsafe impl DangerousTrait for MyType { }
Используется редко (например, Send, Sync для специальных типов).
26. Расширенные паттерны сопоставления
26.1. Деструктуризация
Структуры:
struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
// или
let Point { x, y } = p; // если имена совпадают
Кортежи и перечисления:
enum Message {
Quit,
Move { x: i32, y: i32 },
}
let msg = Message::Move { x: 1, y: 2 };
match msg {
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
_ => (),
}
26.2. Охранники (if в match)
match value {
n if n < 0 => println!("Negative"),
n if n > 0 => println!("Positive"),
_ => println!("Zero"),
}
26.3. Диапазоны
match age {
0..=12 => println!("Child"),
13..=19 => println!("Teen"),
_ => println!("Adult"),
}
Поддерживаются:
..— исключает правую границу..=— включает правую границу
26.4. Игнорирование значений
_— игнорировать значение..— игнорировать остаток структуры/кортежа
let numbers = (1, 2, 3, 4, 5);
let (first, .., last) = numbers;
27. Стандартная библиотека — обзор ключевых модулей
27.1. std::collections
Vec<T>— динамический массивHashMap<K, V>— хеш-таблицаHashSet<T>— множествоLinkedList<T>,VecDeque<T>— двусвязный список и декBinaryHeap<T>— макс-куча
27.2. std::fs — работа с файловой системой
use std::fs;
let contents = fs::read_to_string("file.txt")?;
fs::write("output.txt", "Данные")?;
27.3. std::net — сетевое взаимодействие
TCP-сервер:
use std::net::TcpListener;
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() {
// обработка соединения
}
27.4. std::time — время и задержки
use std::time::{Duration, Instant};
let now = Instant::now();
thread::sleep(Duration::from_secs(1));
println!("Elapsed: {:?}", now.elapsed());
27.5. std::env — окружение
let args: Vec<String> = env::args().collect();
let home = env::var("HOME").unwrap();
27.6. std::process — управление процессами
use std::process::Command;
let output = Command::new("ls").output().unwrap();
println!("{}", String::from_utf8_lossy(&output.stdout));
28. Производительность и профилирование
28.1. Оптимизация на уровне компилятора
Rust использует LLVM в качестве бэкенда. Уровни оптимизации:
opt-level = 0— отладка, без оптимизацийopt-level = 1..3— увеличение агрессивности оптимизацийopt-level = "s"— минимизация размераopt-level = "z"— максимальное сжатие (как"s", но с дополнительными мерами)
Рекомендации:
- Используйте
--releaseдля измерения производительности. - Включайте
lto = trueиcodegen-units = 1в релизных сборках. - Избегайте избыточного копирования: предпочитайте
&strвместоStringпри чтении.
28.2. Профилирование
Инструменты:
- perf (Linux) — системный профайлер
- Instruments (macOS) — встроенный профайлер
- VTune, Callgrind, FlameGraph — для детального анализа
Пример с FlameGraph:
cargo build --release
perf record target/release/my_app
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
28.3. Измерение времени выполнения
Для микро-бенчмарков — используйте criterion:
use criterion::{criterion_group, criterion_main, Criterion};
fn benchmark_add(c: &mut Criterion) {
c.bench_function("add", |b| b.iter(|| add(2, 3)));
}
criterion_group!(benches, benchmark_add);
criterion_main!(benches);
Запуск: cargo bench
28.4. Аллокации и память
- Минимизируйте количество выделений в куче.
- Используйте
Vec::with_capacity()при известном размере. - Рассмотрите
SmallVec,ArrayVecдля малых коллекций (из crates.io). - Избегайте
.clone()без необходимости — передавайте ссылки.
29. Обработка ошибок на продакшене
29.1. Иерархия ошибок
Создайте собственный тип ошибки:
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
Custom(String),
}
Или используйте библиотеки:
thiserror— для библиотек (реализуетstd::error::Error)anyhow— для приложений (гибкая обёртка над ошибками)
Пример с thiserror:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("Invalid port: {0}")]
InvalidPort(#[from] std::num::ParseIntError),
#[error("Missing config file")]
MissingFile,
}
29.2. Логирование
Используйте tracing или log + env_logger:
use tracing::{info, error};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
info!("Application started");
error!("Something went wrong");
}
29.3. Отказоустойчивость
- Оборачивайте задачи в
tokio::spawnс обработкой паник. - Используйте
Resultвместоpanic!в публичном API. - Предусматривайте тайм-ауты для внешних вызовов.
30. Лучшие практики и идиомы Rust
30.1. Владение и заимствование
- Передавайте владение только тогда, когда значение больше не нужно у вызывающего.
- Используйте
&Tдля чтения,&mut Tдля изменения. - Избегайте
Rc<RefCell<T>>в новых проектах — предпочитайте архитектуру без разделяемого состояния.
30.2. API-дизайн
- Возвращайте
Resultдля операций, которые могут завершиться неудачей. - Используйте
Into<T>иAsRef<T>для гибкости входных параметров:
fn process_path<P: AsRef<Path>>(path: P) { ... }
- Предоставляйте конструкторы (
new,from_*) и билдеры для сложных типов.
30.3. Типажи и обобщения
- Ограничивайте обобщения минимально необходимыми трейтами.
- Используйте
impl Traitв возвращаемых типах для упрощения сигнатур. - Избегайте избыточной обобщённости — конкретные типы упрощают понимание.
30.4. Безопасность
- Минимизируйте использование
unsafe. - Если
unsafeнеобходим — изолируйте его в небольшой модуль с чёткими инвариантами. - Документируйте все предусловия для
unsafeблоков.
31. Инструменты экосистемы
31.1. rustfmt
Автоматическое форматирование кода. Конфигурация через rustfmt.toml.
Запуск:
cargo fmt
31.2. clippy
Статический анализатор, находящий антипаттерны.
Запуск:
cargo clippy
cargo clippy -- -D warnings # treat as errors
Популярные линты:
unwrap_used— рекомендует заменитьunwrap()на безопасную обработкуmissing_docs— требует документациюtoo_many_arguments— сигнализирует о проблемах проектирования
31.3. miri
Интерпретатор MIR для обнаружения неопределённого поведения (UB).
Запуск:
cargo +nightly miri test
Находит:
- Использование неинициализированной памяти
- Нарушения правил aliasing
- Арифметику с переполнением (в debug)
31.4. cargo-expand
Показывает раскрытый код макросов.
Установка:
cargo install cargo-expand
Использование:
cargo expand my_macro
31.5. cargo-deny
Проверяет лицензии, зависимости, дублирование.
32. Отладка и диагностика
32.1. Отладочные сообщения
dbg!(value); // печатает файл, строку и значение
32.2. Отладка в IDE
- Rust Analyzer — основной LSP-сервер (поддержка в VS Code, JetBrains, Vim)
- Поддержка перехода к определению, рефакторинга, подсказок типов
32.3. Анализ паник
Используйте RUST_BACKTRACE=1:
RUST_BACKTRACE=1 cargo run
Варианты:
RUST_BACKTRACE=short— краткий трейсRUST_BACKTRACE=full— полный трейс
32.4. Проверка утечек памяти
- Rust не имеет сборщика мусора, но утечки возможны через циклические ссылки (
Rc/Arc). - Используйте
Weak<T>для разрыва циклов. - Проверяйте счётчики ссылок в тестах.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Инициатором проекта стал Graydon Hoare, тогда — независимый исследователь и разработчик, работавший в Mozilla с 2006 года. До этого он участвовал в разработке компиляторов и языковых инструментов,… Фундамент для начинающего программиста - что повторить, как работать, чего ожидать. Набор советов, правил, принципов и обычаев в разработке на этом языке. Черты могут иметь методы по умолчанию. Если тип не переопределяет метод, используется версия из черты. Это позволяет расширять функциональность без изменения базового кода. fn - ключевое слово, которое обозначает начало объявления функции. Функция представляет собой именованный блок кода, выполняющий конкретную задачу. В данном случае функция называется main. Rust — это язык системного программирования, сочетающий безопасность памяти, высокую производительность и выразительность. Его экосистема охватывает широкий спектр областей — от встраиваемых систем… Системное программирование — это разработка программного обеспечения, отвечающего за взаимодействие с аппаратной частью компьютера и операционной системой. Такое ПО создаёт фундамент, на котором… Кавычки, точки, запятые, скобки и прочие знаки препинания. Ключевое слово Значение -------------------------- abstract Зарезервировано для будущих версий языка become Зарезервировано для будущих версий языка box Зарезервировано для будущих версий языка do… Макрос / Атрибут Назначение ------------------------------ test Пометка функции как теста для cargo test should_panic Ожидание паники при выполнении теста ignore Пропуск теста при обычном запуске… Типизация, набор правил определения типа данных значений языка. Циклы в Rust — это конструкции, предназначенные для многократного выполнения блока кода до тех пор, пока выполняется определённое условие или не исчерпан набор данных. В отличие от многих других…История языка Rust
Что требуется знать перед началом изучения языка программирования Rust
Рекомендации по разработке на Rust
Rust для начинающих
Основы языка Rust
Экосистема приложений на Rust
Системное программирование на Rust
Синтаксис и пунктуация в Rust
Ключевые слова языка Rust
Встроенные функции и стандартная библиотека
Типы данных и владение памятью
Управляющие конструкции и циклы в Rust